package com.itbour.common.util;

import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * copyright    Founder 2010
 * @date     2009/12/22 V1.1修正
 * @author      張怡勛
 * @version     V1.1
 * @date     2009/11/24 V1.0新規作成
 * @author      柳蔚蔚
 * @version     V1.0
 * @brief GregorianCalendarは，0:一月, 1:2月，，であるので， getMonth(),set(int, int, int)の月が， 1:一月，，になるようにしている
 * 新暦である明治６年（１８７３年）移行をサポート。
 * @since 1.0
 */
public class JaCalendar
{
    /**
     * 平成 H
     */
    public static final String HEISEI = "H";

    /**
     * 昭和 S
     */
    public static final String SHOWA = "S";

    /**
     * 大正 T
     */
    public static final String TAISHO = "T";

    /**
     * 明治 M
     */
    public static final String MEIJI = "M";

    /**
     * 未サポート U
     */
    public static final String UNSUPPORTED = "未サポート";

    /**
     * 明治の開始日 01/09/08
     */
    static final int[] FM = { 1, 9, 8 };

    /**
     * 明治の最終日 45/07/29
     */
    static final int[] LM = { 45, 7, 29 };

    /**
     * 大正の開始日 01/07/30
     */
    static final int[] FT = { 1, 7, 30 };

    /**
     * 大正の最終日 15/12/24
     */
    static final int[] LT = { 15, 12, 24 };

    /**
     * 昭和の開始日 01/12/25
     */
    static final int[] FS = { 1, 12, 25 };

    /**
     * 昭和の最終日 64/01/07
     */
    static final int[] LS = { 64, 1, 7 };

    /**
     * 平成の開始日 01/01/08
     */
    static final int[] FH = { 1, 1, 8 };

    /**
     * 西暦での旧暦の最終日 1872/12/31 GregorianCalendarの仕様で，日本でいう月数に１を引いた値を月に入れる必要がある。
     */
    private final GregorianCalendar LAST_DAY_OF_LUNAR = new GregorianCalendar(
            1872, 11, 31);

    /**
     * 西暦での明治の開始日 1868/09/08
     */
    private final GregorianCalendar FIRST_DAY_OF_MEIJI = new GregorianCalendar(
            1868, 8, 8);

    /**
     * 西暦での明治の最終日 1912/07/29
     */
    private final GregorianCalendar LAST_DAY_OF_MEIJI = new GregorianCalendar(
            1912, 6, 29);

    /**
     * 西暦での大正の開始日 1912/07/30
     */
    private final GregorianCalendar FIRST_DAY_OF_TAISHO = new GregorianCalendar(
            1912, 6, 30);

    /**
     * 西暦での大正の最終日 1926/12/24
     */
    private final GregorianCalendar LAST_DAY_OF_TAISHO = new GregorianCalendar(
            1926, 11, 24);

    /**
     * 西暦での昭和の開始日 1926/12/25
     */
    private final GregorianCalendar FIRST_DAY_OF_SHOWA = new GregorianCalendar(
            1926, 11, 25);

    /**
     * 西暦での昭和の最終日 1989/01/07
     */
    private final GregorianCalendar LAST_DAY_OF_SHOWA = new GregorianCalendar(
            1989, 0, 7);

    /**
     * 西暦での平成の開始日 1989/01/08
     */
    private final GregorianCalendar FIRST_DAY_OF_HEISEI = new GregorianCalendar(
            1989, 0, 8);

    /**
     * 明治の西暦との差分 1967
     */
    public static final int YEAR_MEIJI = 1867;

    /**
     * 大正の西暦との差分 1911
     */
    public static final int YEAR_TAISHO = 1911;

    /**
     * 昭和の西暦との差分 1925
     */
    public static final int YEAR_SHOWA = 1925;

    /**
     * 平成の西暦との差分 1988
     */
    public static final int YEAR_HEISEI = 1988;

    /**
     * 内部状態 GregorianCalendar
     */
    private GregorianCalendar gcal;

    /**
     * 現時刻の和暦を生成
     */
    public JaCalendar()
    {
        this.gcal = new GregorianCalendar();
    }

    /**
     * 西暦インスタンスから生成
     *
     * @param gcal
     *            西暦
     */
    public JaCalendar(GregorianCalendar gcal)
    {
        this.gcal = gcal;
    }

    /**
     * 年、月、日から生成 GregorianCalendarは，１月が0で表されるため，引数の月数から１を引いて生成
     *
     * @param year
     *            年
     * @param month
     *            月
     * @param day
     *            日
     */
    public JaCalendar(int year, int month, int day)
    {
        this.gcal = new GregorianCalendar(year, month - 1, day);
    }

    /**
     * 元号、年、月、日から生成
     *
     * @param era
     *            元号
     * @param year
     *            年
     * @param month
     *            月
     * @param day
     *            日
     */
    public JaCalendar(String era, int year, int month, int day)
    {
        if (JaCalendar.check(era, year, month, day))
        {
            if (era.equals(HEISEI))
            {
                this.gcal = new GregorianCalendar(year + YEAR_HEISEI,
                        month - 1, day);
            }

            if (era.equals(SHOWA))
            {
                this.gcal = new GregorianCalendar(year + YEAR_SHOWA, month - 1,
                        day);
            }

            if (era.equals(TAISHO))
            {
                this.gcal = new GregorianCalendar(year + YEAR_TAISHO,
                        month - 1, day);
            }

            if (era.equals(MEIJI))
            {
                this.gcal = new GregorianCalendar(year + YEAR_MEIJI, month - 1,
                        day);
            }
        }
        else
        {
            this.gcal = null;
        }
    }

    /**
     * GYYMMDD形式のStringから生成
     *
     * @param dateString
     *            日付文字列
     */
    public JaCalendar(String dateString)
    {
        final int strLen = dateString.length();

        if (strLen == 7)
        {
            String gengou = dateString.substring(0, 1);
            final int year = Integer.parseInt(dateString.substring(1, 3));
            final int month = Integer.parseInt(dateString.substring(3, 5));
            final int day = Integer.parseInt(dateString.substring(5, 7));

            if (check(gengou, year, month, day))
            {
                if (gengou.equals(HEISEI))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_HEISEI,
                            month - 1, day);
                }

                if (gengou.equals(SHOWA))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_SHOWA,
                            month - 1, day);
                }

                if (gengou.equals(TAISHO))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_TAISHO,
                            month - 1, day);
                }

                if (gengou.equals(MEIJI))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_MEIJI,
                            month - 1, day);
                }
            }
            else
            {
                this.gcal = null;
            }
        }
        else if (strLen == 9)
        {
            String gengou = dateString.substring(0, 1);
            final int year = Integer.parseInt(dateString.substring(1, 3));
            final int month = Integer.parseInt(dateString.substring(4, 6));
            final int day = Integer.parseInt(dateString.substring(7, 9));

            if (check(gengou, year, month, day))
            {
                if (gengou.equals(HEISEI))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_HEISEI,
                            month - 1, day);
                }

                if (gengou.equals(SHOWA))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_SHOWA,
                            month - 1, day);
                }

                if (gengou.equals(TAISHO))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_TAISHO,
                            month - 1, day);
                }

                if (gengou.equals(MEIJI))
                {
                    this.gcal = new GregorianCalendar(year + YEAR_MEIJI,
                            month - 1, day);
                }
            }
        }
        else if (strLen == 8)
        {
            final int year = Integer.parseInt(dateString.substring(0, 4));
            final int month = Integer.parseInt(dateString.substring(4, 6));
            final int day = Integer.parseInt(dateString.substring(6, 8));

            this.gcal = new GregorianCalendar(year, month - 1, day);
        }
        else if (strLen == 10)
        {
            final int year = Integer.parseInt(dateString.substring(0, 4));
            final int month = Integer.parseInt(dateString.substring(5, 7));
            final int day = Integer.parseInt(dateString.substring(8, 10));

            this.gcal = new GregorianCalendar(year, month - 1, day);
        }
        else
        {
            this.gcal = null;
        }
    }

    /**
     * 和暦チェック
     *
     * @param gengou
     *            元号
     * @param year
     *            年
     * @param month
     *            月
     * @param day
     *            日
     * @return boolean
     */
    public static boolean check(String gengou, int year, int month, int day)
    {
        char[] cr = gengou.toCharArray();
        final char c = cr[0];

        if ((c != 'M') && (c != 'T') && (c != 'S') && (c != 'H'))
        {
            return false;
        }

        int[] cd = new int[3];
        cd[0] = year;
        cd[1] = month;
        cd[2] = day;

        if (year < 0)
        {
            return false;
        }

        if (gengou.equals(MEIJI))
        {
            if ((JaCalendar.compareTo(cd, FM) == -1)
                || (JaCalendar.compareTo(cd, LM) == 1))
            {
                return false;
            }
        }
        else if (gengou.equals(TAISHO))
        {
            if ((JaCalendar.compareTo(cd, FT) == -1)
                || (JaCalendar.compareTo(cd, LT) == 1))
            {
                return false;
            }
        }
        else if (gengou.equals(SHOWA))
        {
            if ((JaCalendar.compareTo(cd, FS) == -1)
                || (JaCalendar.compareTo(cd, LS) == 1))
            {
                return false;
            }
        }
        else if (gengou.equals(HEISEI))
        {
            if (JaCalendar.compareTo(cd, FH) == -1)
            {
                return false;
            }
        }

        return true;
    }

    /**
     * 元号を取得
     *
     * @return String
     */
    public String getEra()
    {
        if (this.gcal == null)
        {
            return "";
        }

        if (this.gcal.after(LAST_DAY_OF_SHOWA))
        {
            return HEISEI;
        }
        else if (this.gcal.after(LAST_DAY_OF_TAISHO))
        {
            return SHOWA;
        }
        else if (this.gcal.after(LAST_DAY_OF_MEIJI))
        {
            return TAISHO;
        }
        else if (this.gcal.after(LAST_DAY_OF_LUNAR))
        {
            return MEIJI;
        }
        else
        {
            return UNSUPPORTED;
        }
    }

    /**
     * 和暦の年を取得
     *
     * @return int
     */
    public int getYear()
    {
        if (this.gcal == null)
        {
            return 0;
        }

        if (this.gcal.after(LAST_DAY_OF_SHOWA))
        {
            return this.gcal.get(GregorianCalendar.YEAR) - YEAR_HEISEI;
        }
        else if (this.gcal.after(LAST_DAY_OF_TAISHO))
        {
            return this.gcal.get(GregorianCalendar.YEAR) - YEAR_SHOWA;
        }
        else if (this.gcal.after(LAST_DAY_OF_MEIJI))
        {
            return this.gcal.get(GregorianCalendar.YEAR) - YEAR_TAISHO;
        }
        else if (this.gcal.after(LAST_DAY_OF_LUNAR))
        {
            return this.gcal.get(GregorianCalendar.YEAR) - YEAR_MEIJI;
        }
        else
        {
            return 0;
        }
    }

    /**
     * 元号，年月日をセット
     *
     * @param year
     *            年
     * @param month
     *            月
     * @param day
     *            日
     * @param era
     *            元号
     */
    public void set(String era, int year, int month, int day)
    {
        if (era.equals(HEISEI))
        {
            this.gcal.set(year + YEAR_HEISEI, month, day);
        }

        if (era.equals(SHOWA))
        {
            this.gcal.set(year + YEAR_SHOWA, month, day);
        }

        if (era.equals(TAISHO))
        {
            this.gcal.set(year + YEAR_TAISHO, month, day);
        }

        if (era.equals(MEIJI))
        {
            this.gcal.set(year + YEAR_MEIJI, month, day);
        }
    }

    /**
     * EYY/MM/DDのStringに変換。
     *
     * @return String
     */
    public String toString()
    {
        String delimiter = "/";

        return getEra() + Integer.toString(getYear()) + delimiter
            + Integer.toString(getMonth()) + delimiter
            + Integer.toString(getDay());
    }

    /**
     * 西暦文字列YYYY/MM/DDのStringに変換。
     *
     * @return String
     */
    public String toEnString()
    {
        String delimiter = "/";

        return Integer.toString(this.gcal.get(Calendar.YEAR)) + delimiter
            + Integer.toString(getMonth()) + delimiter
            + Integer.toString(getDay());
    }

    /**
     * 元号YY年MM年DD日のStringに変換
     *
     * @return String
     */
    public String toJaString()
    {
        String kg = "";

        if (getEra().equals(MEIJI))
        {
            kg = "明治";
        }
        else if (getEra().equals(TAISHO))
        {
            kg = "大正";
        }
        else if (getEra().equals(SHOWA))
        {
            kg = "昭和";
        }
        else if (getEra().equals(HEISEI))
        {
            kg = "平成";
        }

        final int year = getYear();
        String jyear = "";

        if (year == 1)
        {
            jyear = "元";
        }
        else
        {
            jyear = Integer.toString(year);
        }

        return kg + jyear + "年"
            + Integer.toString(this.gcal.get(GregorianCalendar.MONTH) + 1)
            + "月"
            + Integer.toString(this.gcal.get(GregorianCalendar.DAY_OF_MONTH))
            + "日";
    }

    /**
     * 元号YY/MM/DD
     *
     * @return String
     */
    public String toJa9String()
    {
        if(!this.isValid())
        {
            return null;
        }
        int month = this.gcal.get(GregorianCalendar.MONTH) + 1;
        int day = this.gcal.get(GregorianCalendar.DAY_OF_MONTH);
        String kg = getEra();
        return kg + getYear() + "/"
            + (month < 10 ? "0" + month : month)
            + "/"
            + (day < 10 ? "0" + day : day);
    }

    /**
     * 月の取得
     *
     * @return int
     */
    public int getMonth()
    {
        return this.gcal.get(GregorianCalendar.MONTH) + 1;
    }

    /**
     * 日の取得
     *
     * @return int
     */
    public int getDay()
    {
        return this.gcal.get(GregorianCalendar.DAY_OF_MONTH);
    }

    /**
     * GregorianCalendarのgetに転送する
     *
     * @param field
     *            java.util.GregorianCalendarのフィールド
     * @return int
     */
    public int get(int field)
    {
        if (this.gcal != null)
        {
            return this.gcal.get(field);
        }
        else
        {
            return 0;
        }
    }

    /**
     * 有効かどうかのチェック
     *
     * @return boolean
     */
    public boolean isValid()
    {
        return (this.gcal != null);
    }

    /**
     * 西暦の年月日をセット
     *
     * @param year
     *            年
     * @param month
     *            月
     * @param date
     *            日
     */
    public void set(int year, int month, int date)
    {
        this.gcal.set(year, month - 1, date);
    }

    /**
     * インスタンスの大小比較
     *
     * @return int 第２が第１よりも大：１，同じ：０，小：－１
     * @param lDate
     *            Date1
     * @param gDate
     *            Date2
     */
    public static int compareTo(int[] lDate, int[] gDate)
    {
        final int year = lDate[0];
        final int month = lDate[1];
        final int day = lDate[2];

        final int gYear = gDate[0];
        final int gMonth = gDate[1];
        final int gDay = gDate[2];

        if (year < gYear)
        {
            return -1;
        }
        else if (year == gYear)
        {
            if (month < gMonth)
            {
                return -1;
            }
            else if (month == gMonth)
            {
                if (day < gDay)
                {
                    return -1;
                }
                else if (day == gDay)
                {
                    return 0;
                }
            }
        }

        return 1;
    }
}
